/*
 * dnsclient.c
 * Authors: Doris, Stephanie
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <time.h>
#include <serialize_deserialize_dns_names.h>


#define MAXBUF 512
#define MAXLINE 65535

int main(int argc, char *argv[]) {
	struct addrinfo hints, *servinfo, *p;
	struct sockaddr_storage remoteaddr;
	size_t addr_len;
	int rv;
	int numbytes;
	const char * server_port;
	char * nodename;
	char * servip;
	char * nodeip;
	char msg[MAXLINE];
	int ttl;
	int msgsize;
	int id=0;
	int i;

	fd_set master;
	fd_set read_fds;
	int fdmax;
	short sockfd;

	if(argc == 6) {
		server_port = argv[2];
		nodename = argv[3];
		servip = argv[1];
		nodeip = argv[4];
		ttl = atoi(argv[5]);
	} else {
		perror("Wrong number of arguments.\n");
		exit(1);
	}

	FD_ZERO(&master);
	FD_ZERO(&read_fds);

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;

	if((rv = getaddrinfo(nodename, server_port, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		return 1;
	}

	for(p = servinfo; p != NULL; p = p->ai_next) {
		if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
			perror("dnsclient: socket");
			continue;
		}
		break;
	}

	if(p == NULL) {
		fprintf(stderr, "talker: failed to bind socket\n");
		return 2;
	}

	freeaddrinfo(servinfo);

	FD_SET(0, &master);		//stdin
	FD_SET(sockfd, &master);		//listening on server
	fdmax = sockfd;

	int canExit = 0;
	int timesTried = 0;
	while ((canExit == 0) && (timesTried < 3)) { // haven't heard/received response from server yet
		// DNS message that will be sent to server
		int domainNameLen = 0;
		char *domainName = (char *) malloc((strlen(nodename)+3)*sizeof(char));

		int succeed = serialize_DNS_name(domainName, strlen(nodename)+3, &domainNameLen, nodename);
		if (succeed == 0) {
			printf("serialize dns name failed\n");
			exit(1);
		}

		int resourceDataLen =  strlen(nodeip);

		/*********************************************************************
		 * INSTALL MAPPING
		 *
		 *********************************************************************/
		//char *dnsMsg = (char*) malloc ((208+resourceDataLen+domainNameLen) * sizeof(char));
		char dnsMsg[24+domainNameLen+resourceDataLen];
		bzero(dnsMsg, 24+resourceDataLen+domainNameLen);

		// Add info the DNS message [INSTALL MAPPING]

		//set id
		for (i = 0; i < 2; i++){
			dnsMsg[i] = (id>>(8-(2*i))) & 0x0f;
		}
		id++;

		//set Parameter
		for (i = 0; i <2; i++){// Bit 0 = 1
			if (i == 0)
				dnsMsg[i+2] = 0x80;
			dnsMsg[i+2] = 0;
		}

		// set Number of Question
		for (i = 0; i <2; i++) {
				dnsMsg[i+4] = 0;
		}

		// set Number of Answer
		for (i = 0; i <2; i++) {
			if (i == 0)
				dnsMsg[i+6]= 0;
			else
				dnsMsg[i+6] = 0x01;
		}

		// set Number of Authority
		for (i = 0; i <2; i++) {
				dnsMsg[i+8] = 0;
		}

		// set Number of Additional
		for (i = 0; i <2; i++) {
				dnsMsg[i+10] = 0;
		}
		//Don't need to set Question[Domain Name]

		//set Question[Type]
		for (i = 0; i < 2; i++) {
			if (i == 0)
				dnsMsg[i+12] = 0;
			else
				dnsMsg[i+12] = 0x01;
		}

		//set Question[Class]
		for (i = 0; i < 2; i++) {
			if (i == 0)
				dnsMsg[i+14] = 0;
			else
				dnsMsg[i+14] = 0x01;
		}

		//set Answers[Domain Name]
		for (i = 0; i < domainNameLen; i++){
			dnsMsg[i+16] = domainName[i];
		}

		//set Answer[Type]
		for (i = 0; i < 2; i++) {
			if (i == 0)
				dnsMsg[i+16+domainNameLen] = 0;
			else
				dnsMsg[i+16+domainNameLen] = 0x01;
		}

		//set Answer[Class]
		for (i = 0; i < 2; i++) {
			if (i == 0)
				dnsMsg[i+18+domainNameLen] = 0;
			else
				dnsMsg[i+18+domainNameLen] = 0x01;
		}

		//set Answer[TTL]
		for (i = 0; i < 4; i++) {
			dnsMsg[i+(20+domainNameLen)] = (ttl>>(31-(8*i))) & 0x0f;
		}

		//set Answer[Resource Data Length]
		for (i = 0; i < 2; i++) {
			dnsMsg[i+(22+domainNameLen)] = (resourceDataLen>>(15-(8*i))) & 0x0f;
		}

		//set Answer[Resource Data]
		for (i = 0; i < resourceDataLen; i++) {
			dnsMsg[i+(24+domainNameLen)] = nodeip[i];
		}

		//send DNS message to server
		if((numbytes = sendto(sockfd, dnsMsg, 24+domainNameLen+resourceDataLen, 0, p->ai_addr, p->ai_addrlen)) == -1) {
			perror("dnsclient: sendto\n");
			exit(1);
		}
		printf("Message Sent\n");

		//free(dnsMsg);
		/*************************************************************************
		 * Send Query
		 *
		 *************************************************************************/

		//dnsMsg = (char*) malloc ((208+resourceDataLen+domainNameLen) * sizeof(char));
		char queryMsg[24+domainNameLen];
		bzero(queryMsg, 24+domainNameLen);

		//set id
		for (i = 0; i < 2; i++){
			queryMsg[i] = (id>>(8-(2*i))) & 0x0f;
		}
		id++;

		//set Parameter
		for (i = 0; i <2; i++){// Bit 0 = 1
			if (i == 0)
				queryMsg[i+2] = 0x80;
			queryMsg[i+2] = 0;
		}

		// set Number of Question
		for (i = 0; i <2; i++) {
			if (i == 0)
				queryMsg[i+4] = 0;
			else
				queryMsg[i+4] = 0x01;
		}

		// set Number of Answer
		for (i = 0; i <2; i++) {
				queryMsg[i+6]= 0;
		}

		// set Number of Authority
		for (i = 0; i <2; i++) {
				queryMsg[i+8] = 0;
		}

		// set Number of Additional
		for (i = 0; i <2; i++) {
				queryMsg[i+10] = 0;
		}

		//set Question[Domain Name]
		for (i = 0; i < domainNameLen; i++){
			queryMsg[i+12] = domainName[i];
		}

		//set Question[Type]
		for (i = 0; i < 2; i++) {
			if (i == 0)
				queryMsg[i+12+domainNameLen] = 0;
			else
				queryMsg[i+12+domainNameLen] = 0x01;
		}

		//set Question[Class]
		for (i = 0; i < 2; i++) {
			if (i == 0)
				queryMsg[i+14+domainNameLen] = 0;
			else
				queryMsg[i+14+domainNameLen] = 0x01;
		}

		//Don't need to set Answers[Domain Name]

		//set Answer[Type]
		for (i = 0; i < 2; i++) {
			if (i == 0)
				queryMsg[i+16+domainNameLen] = 0;
			else
				queryMsg[i+16+domainNameLen] = 0x01;
		}

		//set Answer[Class]
		for (i = 0; i < 2; i++) {
			if (i == 0)
				queryMsg[i+18+domainNameLen] = 0;
			else
				queryMsg[i+18+domainNameLen] = 0x01;
		}

		//set Answer[TTL]
		for (i = 0; i < 4; i++) {
			queryMsg[i+(20+domainNameLen)] = 0;
		}

		//set Answer[Resource Data Length]
		for (i = 0; i < 2; i++) {
			queryMsg[i+(22+domainNameLen)] = 0;
		}

		if((numbytes = sendto(sockfd, queryMsg, 24+domainNameLen, 0, p->ai_addr, p->ai_addrlen)) == -1) {
			perror("dnsclient: sendto\n");
			exit(1);
		}
		printf("Message Sent\n");

		int start = time(NULL);
		while (time(NULL)-start < 3) {
			if (recvfrom(sockfd, msg, MAXLINE , 0, (struct sockaddr *)&remoteaddr, &addr_len) > 0) {

				//set id
				int rId = 0;
				for (i = 0; i < 2; i++){
					rId = rId << (i*8);
					rId = rId || msg[i];
				}

				if (id != rId) // wrong response message
					break;

				//set parameter
				int parameter = 0;
				for (i = 0; i < 2; i++){
					parameter = parameter << (i*8);
					parameter = parameter || msg[i+2];
				}

				//set numQ
				int numQ = 0;
				for (i = 0; i < 2; i++){
					numQ = numQ << (8*i);
					numQ = numQ || msg[i+4];
				}

				//set numAns
				int numAns = 0;
				for (i = 0; i < 2; i++){
					numAns = numAns << (8*i);
					numAns = numAns || msg[i+6];
				}
				if (numAns != 1)
					break;

				// get length of DomainName in Questions
				int readSize;
				char *domainName;
				domainName = deserialize_DNS_name(msg+12, 512, &readSize);

				// parsing answer
				domainName = deserialize_DNS_name(msg+16+readSize, 512, &readSize);
				if (readSize != strlen(nodename))
					break;

				int matched = 1;
				for (i = 0; i < readSize; i ++) {
					if (domainName[i] != nodename[i]) {
						matched = 0;
						break;
					}
				}

				if (matched == 0)  // found unmatched
					break;

				int rDL = 0;
				for (i = 0; i < 2; i++){
					rDL = rDL << (8*i);
					rDL = rDL || msg[i+16+readSize+readSize+8];
				}

				matched = 1;
				for (i = 0; i < rDL; i ++) {
					if (nodeip[i] != msg[i+16+readSize+readSize+10]) {
						matched = 0;
						break;
					}
				}

				if (matched == 0)  // found unmatched
					break;

				canExit = 1;
				break;
			}
			//else didn't receive anything, keep trying if under 3 seconds
		}
		timesTried++;
	}

	if (canExit == 0)
		perror("Error: could not install mapping in server.");

	printf("dnsclient: sent %d bytes to %s\n", numbytes, nodename);
	close(sockfd);
	exit(1);

}
